package DARCOServer.Messaging;

import static DARCOShared.utils.Constants.ACCOUNT_AVATAR;
import static DARCOShared.utils.Constants.ACCOUNT_CREATION;
import static DARCOShared.utils.Constants.ACCOUNT_EMAIL;
import static DARCOShared.utils.Constants.ACCOUNT_ID;
import static DARCOShared.utils.Constants.ACCOUNT_KEYPAIR;
import static DARCOShared.utils.Constants.ACCOUNT_LASTLOC;
import static DARCOShared.utils.Constants.ACCOUNT_NAME;
import static DARCOShared.utils.Constants.ACCOUNT_PASSWORD;
import static DARCOShared.utils.Constants.ACCOUNT_SURNAME;
import static DARCOShared.utils.Constants.ACCOUNT_USERNAME;
import static DARCOShared.utils.Constants.EMAIL_PATTERN;

import java.net.InetAddress;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateCrtKey;

import org.tmatesoft.sqljet.core.SqlJetException;
import org.tmatesoft.sqljet.core.table.ISqlJetCursor;

import DARCOServer.db.DatabaseManager;
import DARCOServer.security.SecurityManager;
import DARCOShared.requests.ChangeOwnershipRequest;
import DARCOShared.requests.CreateAccountRequest;
import DARCOShared.requests.GetCardFromQRScanRequest;
import DARCOShared.requests.GetCardImageRequest;
import DARCOShared.requests.GetCardInfoRequest;
import DARCOShared.requests.GetCardJourneyRequest;
import DARCOShared.requests.GetCardListByOwnerRequest;
import DARCOShared.requests.GetPositionsOfNearbyUsersRequest;
import DARCOShared.requests.LoginRequest;
import DARCOShared.requests.RequestBase;
import DARCOShared.requests.RequestWrapper;
import DARCOShared.requests.UpdateUserPositionRequest;
import DARCOShared.responses.CreateAccountResponse;
import DARCOShared.responses.GetCardImageResponse;
import DARCOShared.responses.GetCardListByOwnerResponse;
import DARCOShared.responses.LoginResponse;
import DARCOShared.responses.ResponseBase;
import DARCOShared.responses.ResponseWrapper;
import DARCOShared.utils.MachineSpecificConstants;
import DARCOShared.utils.NetUtils;
import DARCOShared.utils.SecurityUtilities;
import eneter.messaging.endpoints.typedmessages.DuplexTypedMessagesFactory;
import eneter.messaging.endpoints.typedmessages.IDuplexTypedMessageReceiver;
import eneter.messaging.endpoints.typedmessages.IDuplexTypedMessagesFactory;
import eneter.messaging.endpoints.typedmessages.TypedRequestReceivedEventArgs;
import eneter.messaging.messagingsystems.httpmessagingsystem.HttpMessagingSystemFactory;
import eneter.messaging.messagingsystems.messagingsystembase.IDuplexInputChannel;
import eneter.messaging.messagingsystems.messagingsystembase.IDuplexOutputChannel;
import eneter.messaging.messagingsystems.messagingsystembase.IMessagingSystemFactory;
import eneter.messaging.messagingsystems.tcpmessagingsystem.TcpMessagingSystemFactory;
import eneter.messaging.messagingsystems.websocketmessagingsystem.WebSocketMessagingSystemFactory;
import eneter.net.system.EventHandler;

public class MessagingManager {

	private IDuplexTypedMessageReceiver<ResponseWrapper, RequestWrapper> myReceiver;

	public void OpenConnection()
	{
		// Create message receiver receiving 'MyRequest' and receiving 'MyResponse'.
		IDuplexTypedMessagesFactory aReceiverFactory = new DuplexTypedMessagesFactory();
		myReceiver = aReceiverFactory.createDuplexTypedMessageReceiver(ResponseWrapper.class, RequestWrapper.class);

		// Subscribe to handle messages.
		myReceiver.messageReceived().subscribe(OnMessageReceivedEventHandler);

		// Create TCP messaging.
		//IMessagingSystemFactory aMessaging = new WebSocketMessagingSystemFactory();
		IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();

		try {
			IDuplexInputChannel anInputChannel = aMessaging.createDuplexInputChannel("tcp://"+ MachineSpecificConstants.getLocalIP() + ":8060/darco");
			//IDuplexInputChannel anInputChannel = aMessaging.createDuplexInputChannel("tcp://"+ MachineSpecificConstants.getLocalIP() + ":8060/");


			// Attach the input channel and start to listen to messages.
			myReceiver.attachDuplexInputChannel(anInputChannel);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public void CloseConnection()
	{
		// Detach output channel and stop listening to response messages.
		myReceiver.detachDuplexInputChannel();
	}

	// It is called when a message is received.

	private void OnMessageReceived(Object sender, TypedRequestReceivedEventArgs<RequestWrapper> e)
	{
		ResponseBase myResponse = null;
		RSAPrivateCrtKey k = SecurityManager.getPrivateKey();
		RequestBase reqbase = e.getRequestMessage().getRequest(k);
		switch (reqbase.getRequestType()){
		case GetCardInfo :
			GetCardInfoRequest req = (GetCardInfoRequest)reqbase;
			myResponse = DatabaseManager.getCardInfo(req);
			break;
		case GetCardCatalog :
			//ecc...
			break;
		case GetCardJourney:
			myResponse = DatabaseManager.getCardJourney((GetCardJourneyRequest)reqbase);
			break;
		case GetNearestOwners:
			myResponse = DatabaseManager.getPositionsFromRequest((GetPositionsOfNearbyUsersRequest)reqbase);
			break;
		case RegisterCard:
			break;
		case UpdateUserPosition:
			myResponse = DatabaseManager.UpdateUserPosition((UpdateUserPositionRequest)reqbase);
			break;
		case ChangeOwnership:
			myResponse = DatabaseManager.ChangeCardOwnership((ChangeOwnershipRequest)reqbase);
			break;
		case Login:{
			LoginRequest log_req= (LoginRequest) reqbase;
			//fetch the data from database
			ISqlJetCursor login_cursor = DatabaseManager.getAccountByEmail(log_req.email);

			//ho trovato qualcosa nel database

			//response to client
			try {
				if( login_cursor==null || login_cursor.eof())
				{
					myResponse = new LoginResponse("Errore account non presente: ERR00");
					break;
				}	
				//System.out.println(log_req.passwordMD5 +" == "+ login_cursor.getString(ACCOUNT_PASSWORD));
				if((log_req.passwordMD5).compareTo(login_cursor.getString(ACCOUNT_PASSWORD)) != 0){
					//la mail e la password non coincidono
					myResponse= new LoginResponse("Password non corretta");
				}
				else{
					//OK la mail e la passoword coincidono
					long account_id=login_cursor.getInteger(ACCOUNT_ID);
					int x=(int) account_id;
					myResponse= new LoginResponse(x,
							login_cursor.getString(ACCOUNT_EMAIL),  login_cursor.getString(ACCOUNT_NAME),
							login_cursor.getString(ACCOUNT_SURNAME), login_cursor.getString(ACCOUNT_USERNAME),
							login_cursor.getString(ACCOUNT_PASSWORD), login_cursor.getString(ACCOUNT_LASTLOC),
							login_cursor.getString(ACCOUNT_CREATION), login_cursor.getBlobAsArray(ACCOUNT_KEYPAIR), 
							login_cursor.getBlobAsArray(ACCOUNT_AVATAR));
				}
			} catch (SqlJetException e1) {
				myResponse = new LoginResponse("Errore account non presente: ERR01");
				e1.printStackTrace();
			}
			break;
		}
		case CreateAccount:{
			CreateAccountRequest create_req = (CreateAccountRequest) reqbase;
			if(create_req.user==""){
				myResponse= new CreateAccountResponse("username not correct");
				break;}
			if(create_req.name==""){
				myResponse= new CreateAccountResponse("name not correct");
				break;}
			if(create_req.surname==""){
				myResponse= new CreateAccountResponse("surname not correct");
				break;}
			if(create_req.password==""){
				myResponse= new CreateAccountResponse("password empty");
				break;}
			if(!create_req.email.matches(EMAIL_PATTERN)){
				myResponse= new CreateAccountResponse("email not correct");
				break;}

			//elaborazione avatar TODO

			int max=DatabaseManager.getMaxAccountID();
			if (max==-1){
				myResponse = new LoginResponse("Error: on server account id: ERR05");
				break;
			}
			max++;

			ISqlJetCursor create_cursor = DatabaseManager.getAccountByEmail(create_req.email);
			try {
				if( create_cursor!=null && !create_cursor.eof())
				{
					if(create_cursor.getString(ACCOUNT_EMAIL).compareTo(create_req.email)==0)
						myResponse = new LoginResponse("Error: email already used: ERR03");
					create_cursor.close();
					break;
				}	
				else{
					//non c'è gia una mail relativa alla richiesta
					
					if(create_cursor!=null){create_cursor.close();}
				
					KeyPair generated = SecurityUtilities.generateRSAKeypair(1024);
					
					DatabaseManager.insertNewAccount(max, create_req.email, create_req.name, create_req.surname, create_req.user, 
							create_req.password, SecurityUtilities.SerializeToBin(generated), create_req.avatar);
					
					myResponse= new CreateAccountResponse(); //andato tutto liscio
					break;
				}

			}catch(SqlJetException ex){
				ex.printStackTrace();
				break;
			}
		}
		case GetCardListByOwner:
			GetCardListByOwnerRequest reqlist= (GetCardListByOwnerRequest) reqbase;
			myResponse= DatabaseManager.getCardListByEmail(reqlist);
			break;
			
		case GetCardFromQRScan:
			GetCardFromQRScanRequest reqqr = (GetCardFromQRScanRequest) reqbase;
			myResponse=DatabaseManager.getCardFromQRScan(reqqr);
			break;
			
		case GetCardImage:
			GetCardImageRequest reqim= (GetCardImageRequest) reqbase;
			myResponse= DatabaseManager.GetCardImage(reqim);
			
		default:
			break;

		}

		// Send the response message back to the client.
		@SuppressWarnings("unchecked")
		IDuplexTypedMessageReceiver<ResponseWrapper, RequestWrapper> localRecevier = (IDuplexTypedMessageReceiver<ResponseWrapper, RequestWrapper>) sender; 
		try {
			//risponde al client
			ResponseWrapper rw = new ResponseWrapper(myResponse);
			localRecevier.sendResponseMessage(e.getResponseReceiverId(), rw);
			
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}


	private EventHandler<TypedRequestReceivedEventArgs<RequestWrapper>> OnMessageReceivedEventHandler =
			new EventHandler<TypedRequestReceivedEventArgs<RequestWrapper>>(){

		@Override
		public void onEvent(Object sender, TypedRequestReceivedEventArgs<RequestWrapper> e) {
			//TODO reinvocare il thread UI potrebbe essere una buona idea!
			OnMessageReceived(sender, e);
		}

	};
	
	
}
